/**
 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "plugins/ecmascript/runtime/base/typed_array_helper-inl.h"
#include "plugins/ecmascript/runtime/base/typed_array_helper.h"

#include "plugins/ecmascript/runtime/base/builtins_base.h"
#include "plugins/ecmascript/runtime/ecma_runtime_call_info.h"
#include "plugins/ecmascript/runtime/ecma_string.h"
#include "plugins/ecmascript/runtime/ecma_vm.h"
#include "plugins/ecmascript/runtime/global_env.h"
#include "plugins/ecmascript/runtime/js_array.h"
#include "plugins/ecmascript/runtime/js_array_iterator.h"
#include "plugins/ecmascript/runtime/js_handle.h"
#include "plugins/ecmascript/runtime/js_hclass.h"
#include "plugins/ecmascript/runtime/js_object-inl.h"
#include "plugins/ecmascript/runtime/js_tagged_value-inl.h"
#include "plugins/ecmascript/runtime/js_tagged_value.h"
#include "plugins/ecmascript/runtime/js_thread.h"
#include "plugins/ecmascript/runtime/js_typed_array.h"
#include "plugins/ecmascript/runtime/object_factory.h"
#include "plugins/ecmascript/runtime/object_operator.h"

#include "plugins/ecmascript/tests/runtime/common/test_helper.h"
#include "utils/bit_utils.h"

// NOLINTNEXTLINE(google-build-using-namespace)
using namespace ark::ecmascript;
// NOLINTNEXTLINE(google-build-using-namespace)
using namespace ark::ecmascript::builtins;

// NOLINTBEGIN(readability-magic-numbers)

namespace ark::test {

class BuiltinsTypedArrayTest : public testing::Test {
public:
    static void SetUpTestCase()
    {
        GTEST_LOG_(INFO) << "SetUpTestCase";
    }

    static void TearDownTestCase()
    {
        GTEST_LOG_(INFO) << "TearDownCase";
    }

    void SetUp() override
    {
        TestHelper::CreateEcmaVMWithScope(instance_, thread_, scope_);
    }

    void TearDown() override
    {
        TestHelper::DestroyEcmaVMWithScope(instance_, scope_);
    }

protected:
    // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
    JSThread *thread_ {nullptr};

    class TestClass {
    public:
        static JSTaggedValue TestForEachFunc(EcmaRuntimeCallInfo *argv)
        {
            JSHandle<JSTaggedValue> key = builtins_common::GetCallArg(argv, 0);
            if (key->IsUndefined()) {
                return JSTaggedValue::Undefined();
            }
            JSArray *jsArray = JSArray::Cast(builtins_common::GetThis(argv)->GetTaggedObject());
            int length = jsArray->GetArrayLength() + 1;
            jsArray->SetArrayLength(argv->GetThread(), length);
            return JSTaggedValue::Undefined();
        }

        static JSTaggedValue TestEveryFunc(EcmaRuntimeCallInfo *argv)
        {
            uint32_t argc = argv->GetArgsNumber();
            if (argc > 0) {
                [[maybe_unused]] int aaa = builtins_common::GetCallArg(argv, 0)->GetInt();
                //  10 : test case
                if (builtins_common::GetCallArg(argv, 0)->GetInt() > 10) {
                    return builtins_common::GetTaggedBoolean(true);
                }
            }
            return builtins_common::GetTaggedBoolean(false);
        }

        static JSTaggedValue TestFilterFunc(EcmaRuntimeCallInfo *argv)
        {
            ASSERT(argv);
            uint32_t argc = argv->GetArgsNumber();
            if (argc > 0) {
                // 10 : test case
                if (builtins_common::GetCallArg(argv, 0)->GetInt() > 10) {
                    return builtins_common::GetTaggedBoolean(true);
                }
            }
            return builtins_common::GetTaggedBoolean(false);
        }

        static JSTaggedValue TestMapFunc(EcmaRuntimeCallInfo *argv)
        {
            int accumulator = builtins_common::GetCallArg(argv, 0)->GetInt();
            accumulator = accumulator * 2;  // 2 : mapped to 2 times the original value
            return builtins_common::GetTaggedInt(accumulator);
        }

        static JSTaggedValue TestFindFunc(EcmaRuntimeCallInfo *argv)
        {
            uint32_t argc = argv->GetArgsNumber();
            if (argc > 0) {
                // 10 : test case
                if (builtins_common::GetCallArg(argv, 0)->GetInt() > 10) {
                    return builtins_common::GetTaggedBoolean(true);
                }
            }
            return builtins_common::GetTaggedBoolean(false);
        }

        static JSTaggedValue TestFindIndexFunc(EcmaRuntimeCallInfo *argv)
        {
            uint32_t argc = argv->GetArgsNumber();
            if (argc > 0) {
                //  10 : test case
                if (builtins_common::GetCallArg(argv, 0)->GetInt() > 10) {
                    return builtins_common::GetTaggedBoolean(true);
                }
            }
            return builtins_common::GetTaggedBoolean(false);
        }

        static JSTaggedValue TestReduceFunc(EcmaRuntimeCallInfo *argv)
        {
            int accumulator = builtins_common::GetCallArg(argv, 0)->GetInt();
            accumulator = accumulator + builtins_common::GetCallArg(argv, 1)->GetInt();
            return builtins_common::GetTaggedInt(accumulator);
        }

        static JSTaggedValue TestReduceRightFunc(EcmaRuntimeCallInfo *argv)
        {
            int accumulator = builtins_common::GetCallArg(argv, 0)->GetInt();
            accumulator = accumulator + builtins_common::GetCallArg(argv, 1)->GetInt();
            return builtins_common::GetTaggedInt(accumulator);
        }

        static JSTaggedValue TestSomeFunc(EcmaRuntimeCallInfo *argv)
        {
            uint32_t argc = argv->GetArgsNumber();
            if (argc > 0) {
                //  10 : test case
                if (builtins_common::GetCallArg(argv, 0)->GetInt() > 10) {
                    return builtins_common::GetTaggedBoolean(true);
                }
            }
            return builtins_common::GetTaggedBoolean(false);
        }
    };

private:
    PandaVM *instance_ {nullptr};
    EcmaHandleScope *scope_ {nullptr};
};

JSTaggedValue CreateBuiltinsJSObject(JSThread *thread, const PandaString &keyCStr)
{
    auto ecmaVm = thread->GetEcmaVM();
    JSHandle<GlobalEnv> env = ecmaVm->GetGlobalEnv();
    JSHandle<JSTaggedValue> dynclass = env->GetObjectFunction();
    ObjectFactory *factory = ecmaVm->GetFactory();

    JSHandle<JSTaggedValue> obj(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(dynclass), dynclass));
    JSHandle<JSTaggedValue> key(factory->NewFromCanBeCompressString(&keyCStr[0]));
    JSHandle<JSTaggedValue> value(thread, JSTaggedValue(1));
    JSObject::SetProperty(thread, obj, key, value);
    return obj.GetTaggedValue();
}

JSTypedArray *CreateTypedArrayFromList(JSThread *thread, const JSHandle<TaggedArray> &array)
{
    auto ecmaVm = thread->GetEcmaVM();
    JSHandle<GlobalEnv> env = ecmaVm->GetGlobalEnv();

    JSHandle<JSTaggedValue> jsarray(JSArray::CreateArrayFromList(thread, array));
    JSHandle<JSFunction> int8Array(env->GetInt8ArrayFunction());
    JSHandle<JSObject> globalObject(thread, env->GetGlobalObject());
    //  6 : test case
    auto ecmaRuntimeCallInfo1 = TestHelper::CreateEcmaRuntimeCallInfo(thread, JSTaggedValue::Undefined(), 6);
    ecmaRuntimeCallInfo1->SetNewTarget(JSTaggedValue(*int8Array));
    ecmaRuntimeCallInfo1->SetThis(JSTaggedValue(*globalObject));
    ecmaRuntimeCallInfo1->SetCallArg(0, jsarray.GetTaggedValue());

    [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread, ecmaRuntimeCallInfo1.get());
    JSTaggedValue result = int8_array::Int8ArrayConstructor(ecmaRuntimeCallInfo1.get());

    EXPECT_TRUE(result.IsECMAObject());
    JSTypedArray *int8arr = JSTypedArray::Cast(reinterpret_cast<TaggedObject *>(result.GetRawData()));
    return int8arr;
}

TEST_F(BuiltinsTypedArrayTest, Species)
{
    auto ecmaVm = thread_->GetEcmaVM();
    JSHandle<GlobalEnv> env = ecmaVm->GetGlobalEnv();
    JSHandle<JSFunction> array(env->GetArrayFunction());
    JSHandle<JSObject> globalObject(thread_, env->GetGlobalObject());

    auto ecmaRuntimeCallInfo1 = TestHelper::CreateEcmaRuntimeCallInfo(thread_, JSTaggedValue::Undefined(), 4);
    ecmaRuntimeCallInfo1->SetFunction(array.GetTaggedValue());
    ecmaRuntimeCallInfo1->SetThis(globalObject.GetTaggedValue());

    [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo1.get());
    JSTaggedValue result = typed_array::GetSpecies(ecmaRuntimeCallInfo1.get());
    ASSERT_TRUE(result.IsECMAObject());
}

// ES2021 23.2.3.13 new Array(1,2,3,4,3).includes(searchElement [ , fromIndex ])
TEST_F(BuiltinsTypedArrayTest, Includes1)
{
    ASSERT_NE(thread_, nullptr);

    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();

    JSHandle<TaggedArray> array(factory->NewTaggedArray(5));
    array->Set(thread_, 0, JSTaggedValue(1));
    array->Set(thread_, 1, JSTaggedValue(2));
    array->Set(thread_, 2, JSTaggedValue(3));
    array->Set(thread_, 3, JSTaggedValue(4));
    array->Set(thread_, 4, JSTaggedValue(3));
    JSHandle<JSTypedArray> obj(thread_, CreateTypedArrayFromList(thread_, array));

    // new Array(1,2,3,4,3).includes(1,0)
    auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread_, JSTaggedValue::Undefined(), 8);
    ecmaRuntimeCallInfo->SetFunction(JSTaggedValue::Undefined());
    ecmaRuntimeCallInfo->SetThis(obj.GetTaggedValue());
    ecmaRuntimeCallInfo->SetCallArg(0, JSTaggedValue(static_cast<int32_t>(1)));
    ecmaRuntimeCallInfo->SetCallArg(1, JSTaggedValue(static_cast<int32_t>(0)));

    [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo.get());
    JSTaggedValue result = typed_array::proto::Includes(ecmaRuntimeCallInfo.get());
    TestHelper::TearDownFrame(thread_, prev);
    ASSERT_EQ(result.GetRawData(), JSTaggedValue(true).GetRawData());
}

// ES2021 23.2.3.13 new Array(1,2,3,4,3).includes(searchElement [ , fromIndex ])
TEST_F(BuiltinsTypedArrayTest, Includes2)
{
    ASSERT_NE(thread_, nullptr);

    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();

    JSHandle<TaggedArray> array(factory->NewTaggedArray(5));
    array->Set(thread_, 0, JSTaggedValue(1));
    array->Set(thread_, 1, JSTaggedValue(2));
    array->Set(thread_, 2, JSTaggedValue(3));
    array->Set(thread_, 3, JSTaggedValue(4));
    array->Set(thread_, 4, JSTaggedValue(3));
    JSHandle<JSTypedArray> obj(thread_, CreateTypedArrayFromList(thread_, array));

    // new Array(1,2,3,4,3).includes(1,3)
    auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread_, JSTaggedValue::Undefined(), 8);
    ecmaRuntimeCallInfo->SetFunction(JSTaggedValue::Undefined());
    ecmaRuntimeCallInfo->SetThis(obj.GetTaggedValue());
    ecmaRuntimeCallInfo->SetCallArg(0, JSTaggedValue(static_cast<int32_t>(1)));
    ecmaRuntimeCallInfo->SetCallArg(1, JSTaggedValue(static_cast<int32_t>(3)));

    [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo.get());
    JSTaggedValue result = typed_array::proto::Includes(ecmaRuntimeCallInfo.get());
    TestHelper::TearDownFrame(thread_, prev);
    ASSERT_EQ(result.GetRawData(), JSTaggedValue(false).GetRawData());
}

// ES2021 23.2.3.13 new Array(1,2,3,4,3).includes(searchElement [ , fromIndex ])
TEST_F(BuiltinsTypedArrayTest, Includes3)
{
    ASSERT_NE(thread_, nullptr);

    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();

    JSHandle<TaggedArray> array(factory->NewTaggedArray(5));
    array->Set(thread_, 0, JSTaggedValue(1));
    array->Set(thread_, 1, JSTaggedValue(2));
    array->Set(thread_, 2, JSTaggedValue(3));
    array->Set(thread_, 3, JSTaggedValue(4));
    array->Set(thread_, 4, JSTaggedValue(3));
    JSHandle<JSTypedArray> obj(thread_, CreateTypedArrayFromList(thread_, array));

    // new Array(1,2,3,4,3).includes(5,0)
    auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread_, JSTaggedValue::Undefined(), 8);
    ecmaRuntimeCallInfo->SetFunction(JSTaggedValue::Undefined());
    ecmaRuntimeCallInfo->SetThis(obj.GetTaggedValue());
    ecmaRuntimeCallInfo->SetCallArg(0, JSTaggedValue(static_cast<int32_t>(5)));
    ecmaRuntimeCallInfo->SetCallArg(1, JSTaggedValue(static_cast<int32_t>(0)));

    [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo.get());
    JSTaggedValue result = typed_array::proto::Includes(ecmaRuntimeCallInfo.get());
    TestHelper::TearDownFrame(thread_, prev);
    ASSERT_EQ(result.GetRawData(), JSTaggedValue(false).GetRawData());
}

// ES2021 23.2.3.13 new Array(1,2,3,4,3).includes(searchElement [ , fromIndex ])
TEST_F(BuiltinsTypedArrayTest, Includes4)
{
    ASSERT_NE(thread_, nullptr);

    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();

    JSHandle<TaggedArray> array(factory->NewTaggedArray(5));
    array->Set(thread_, 0, JSTaggedValue(1));
    array->Set(thread_, 1, JSTaggedValue(2));
    array->Set(thread_, 2, JSTaggedValue(3));
    array->Set(thread_, 3, JSTaggedValue(4));
    array->Set(thread_, 4, JSTaggedValue(3));
    JSHandle<JSTypedArray> obj(thread_, CreateTypedArrayFromList(thread_, array));

    // new Array(1,2,3,4,3).includes(1)
    auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread_, JSTaggedValue::Undefined(), 6);
    ecmaRuntimeCallInfo->SetFunction(JSTaggedValue::Undefined());
    ecmaRuntimeCallInfo->SetThis(obj.GetTaggedValue());
    ecmaRuntimeCallInfo->SetCallArg(0, JSTaggedValue(static_cast<int32_t>(1)));

    [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo.get());
    JSTaggedValue result = typed_array::proto::Includes(ecmaRuntimeCallInfo.get());
    TestHelper::TearDownFrame(thread_, prev);
    ASSERT_EQ(result.GetRawData(), JSTaggedValue(true).GetRawData());
}

// ES2021 23.2.3.13 new Array(1,2,3,4,3).includes(searchElement [ , fromIndex ])
TEST_F(BuiltinsTypedArrayTest, Includes5)
{
    ASSERT_NE(thread_, nullptr);

    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();

    JSHandle<TaggedArray> array(factory->NewTaggedArray(5));
    array->Set(thread_, 0, JSTaggedValue(1));
    array->Set(thread_, 1, JSTaggedValue(2));
    array->Set(thread_, 2, JSTaggedValue(3));
    array->Set(thread_, 3, JSTaggedValue(4));
    array->Set(thread_, 4, JSTaggedValue(3));
    JSHandle<JSTypedArray> obj(thread_, CreateTypedArrayFromList(thread_, array));

    // new Array(1,2,3,4,3).includes(5)
    auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread_, JSTaggedValue::Undefined(), 6);
    ecmaRuntimeCallInfo->SetFunction(JSTaggedValue::Undefined());
    ecmaRuntimeCallInfo->SetThis(obj.GetTaggedValue());
    ecmaRuntimeCallInfo->SetCallArg(0, JSTaggedValue(static_cast<int32_t>(5)));

    [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo.get());
    JSTaggedValue result = typed_array::proto::Includes(ecmaRuntimeCallInfo.get());
    TestHelper::TearDownFrame(thread_, prev);
    ASSERT_EQ(result.GetRawData(), JSTaggedValue(false).GetRawData());
}
}  // namespace ark::test

// NOLINTEND(readability-magic-numbers)
